﻿Public Class CS324TextBox
    Private Declare Function CreateCaret Lib "user32" (ByVal hWnd As IntPtr, _
     ByVal hBitmap As IntPtr, ByVal nWidth As Integer, ByVal nHeight As Integer) As Boolean

    Private Declare Function ShowCaret Lib "user32" (ByVal hWnd As IntPtr) _
     As Boolean

    Private Declare Function HideCaret Lib "user32" (ByVal hwnd As IntPtr) As Boolean

    Private Declare Function DestroyCaret Lib "user32" () As Boolean

    Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer

    Private Structure INPUT_TYPE
        Public dwType As Integer
        Public xi As MOUSEINPUT
    End Structure

    Private Structure MOUSEINPUT
        Public dx As Integer
        Public dy As Integer
        Public mouseData As Integer
        Public dwFlags As Integer
        Public dwtime As Integer
        Public dwExtraInfo As Integer
    End Structure

    Private Declare Function SendInput Lib "user32.dll" ( _
     ByVal nInputs As Integer, ByRef pInputs As INPUT_TYPE, _
     ByVal cbSize As Integer) As Integer

    Public Property multiline() As Boolean
        Get
            Return TextBox1.Multiline
        End Get
        Set(ByVal value As Boolean)
            TextBox1.Multiline = value
        End Set
    End Property

    Public Property switchFocus() As Control
        Get
            Return switchTo
        End Get
        Set(ByVal value As Control)
            switchTo = value
        End Set
    End Property

    Public Property textColor() As System.Drawing.Color
        Get
            Return TextBox1.ForeColor
        End Get
        Set(ByVal value As System.Drawing.Color)
            TextBox1.ForeColor = value
        End Set
    End Property


    Dim chars() As Char = {" ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", ",", "?", Chr(13) + Chr(10)}
    Dim textRepresentation(0) As Integer

    Dim MAX_CHARS_INDEX As Integer
    Dim charsIndex As Integer = 0
    Dim boxIndex As Integer = 0
    Dim enterKeyDown As Boolean = False
    Dim arrowsPressedWithEnter As Boolean = False
    Dim switchTo As Control
    Dim mouseReleased As Boolean

    Dim highlightRight As Boolean

    Const MAX_CHARS As Integer = 32767

    Private Const INPUT_MOUSE = 0
    Private Const INPUT_KEYBOARD = 1
    Private Const INPUT_HARDWARE = 2
    Private Const WM_KEYDOWN As Integer = 256
    Private Const WM_SYSKEYDOWN As Integer = 260
    Private Const WM_KEYUP As Integer = 257
    Private Const WM_SYSKEYUP As Integer = 261
    Private Const MOUSE_MOVE = &H1
    Private Const MOUSE_LEFTDOWN = &H2
    Private Const MOUSE_LEFTUP = &H4

    Private Sub CS324TextBox_Resize(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.SizeChanged
        TextBox1.Size = New Size(Me.Size)
    End Sub

    Private Sub TextBox1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Load
        Dim txtLength As Integer

        mouseReleased = True
        keyMouseUp()

        If TextBox1.Multiline Then
            MAX_CHARS_INDEX = 41
        Else
            MAX_CHARS_INDEX = 40
        End If

        'If the textbox loads with text in it, the text needs to be loaded
        'into the textRepresentation array and properly handled
        If Not TextBox1.Text = "" Then
            txtLength = TextBox1.Text.Length - 1
            TextBox1.Text = TextBox1.Text.ToLower
            ReDim textRepresentation(txtLength)
            boxIndex = 0
            'go through each character in the text box
            For index1 As Integer = 0 To txtLength
                'For each character in the textbox, look through
                'all the characters in the textRepresentation array
                'to find a match.  If no math is found, then the 
                'character location is left as a space.
                For index2 As Integer = 0 To MAX_CHARS_INDEX - 1
                    If TextBox1.Text(index1) = chars(index2) Then
                        textRepresentation(index1) = index2
                    End If
                Next
            Next
            'Set the index to the loaded length
            boxIndex = txtLength
            'Ensure everything is loaded correctly
            'and properly initialized
            arrayToBox()
        End If
    End Sub

    Private Sub TextBox1_MouseDown(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.MouseDown
        boxIndex = TextBox1.SelectionStart - 1

        If boxIndex < 0 Then
            boxIndex = 0
        End If

        charsIndex = textRepresentation(boxIndex)
        mouseReleased = (GetAsyncKeyState(Keys.LButton) = 0)
        keyMouseUp()
    End Sub

    Private Sub TextBox1_Focus(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.GotFocus
        If TextBox1.Text = "" Then
            Clear()
        Else
            charsIndex = textRepresentation(boxIndex)
        End If

        CreateCaret(TextBox1.Handle, IntPtr.Zero, -7, FontHeight)
        If TextBox1.SelectionLength = 0 Then
            ShowCaret(TextBox1.Handle)
        End If

        arrayToBox()
    End Sub

    Private Sub TextBox1_Unfocus(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.LostFocus
        DestroyCaret()

        'If the text box is just spaces then treat it as
        'clear it so it is an empty box.
        Dim encoding As Integer = 0
        Dim arraySize As Integer = textRepresentation.Length - 1
        For index As Integer = 0 To arraySize
            encoding += textRepresentation(index)
        Next
        If encoding = 0 Then
            TextBox1.Clear()
            ReDim textRepresentation(0)
            boxIndex = 0
        End If

        'Trim white space on the right end.
        For index As Integer = textRepresentation.Length - 1 To 1 Step -1
            If textRepresentation(index) = 0 Then
                ReDim Preserve textRepresentation(textRepresentation.Length - 2)
                boxIndex -= 1
                charsIndex = textRepresentation(boxIndex)
            Else
                index = -1
            End If
        Next
    End Sub

    Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
        CreateCaret(TextBox1.Handle, IntPtr.Zero, -7, FontHeight)
        ShowCaret(TextBox1.Handle)
    End Sub

    'Overrides all keyboard stroke handling except arrow keys, tab, return, and escape
    Protected Overrides Function ProcessCmdKey(ByRef msg As Message, ByVal keyData As Keys) As Boolean
        Dim selectionStart As Integer
        Dim selectionLength As Integer

        If Not mouseReleased Then
            If (keyData = Keys.Left) Or (keyData = Keys.NumPad4) Then
                keyMouseMove(-7, 0)
            ElseIf (keyData = Keys.Right) Or (keyData = Keys.NumPad6) Then
                keyMouseMove(7, 0)
            ElseIf (keyData = Keys.Up) Or (keyData = Keys.NumPad8) Then
                keyMouseMove(0, -7)
            ElseIf (keyData = Keys.Down) Or (keyData = Keys.NumPad2) Then
                keyMouseMove(0, 7)
            End If
        End If

        If TextBox1.SelectionLength > 0 Then
            selectionStart = TextBox1.SelectionStart
            selectionLength = TextBox1.SelectionLength
        End If

        If (msg.Msg = WM_KEYDOWN) Or (msg.Msg = WM_SYSKEYDOWN) Then
            If keyData = Keys.Up Or keyData = Keys.NumPad8 Then
                charsIndex = (charsIndex + 1) Mod MAX_CHARS_INDEX
                textRepresentation(boxIndex) = charsIndex
                arrayToBox()
            ElseIf keyData = Keys.Down Or keyData = Keys.NumPad2 Then
                charsIndex -= 1
                If charsIndex < 0 Then
                    charsIndex = MAX_CHARS_INDEX - 1
                End If
                textRepresentation(boxIndex) = charsIndex
                arrayToBox()
            ElseIf keyData = Keys.Right Or keyData = Keys.NumPad6 Then
                If Not enterKeyDown Then
                    If TextBox1.SelectedText = "" Then
                        If (boxIndex + 1) >= TextBox1.Text.Length Then
                            If boxIndex < MAX_CHARS Then
                                'Move right!
                                ReDim Preserve textRepresentation(textRepresentation.Length)
                                boxIndex += 1
                                charsIndex = 0
                            Else
                                Return True
                            End If
                        Else
                            boxIndex += 1
                            charsIndex = textRepresentation(boxIndex)
                        End If
                    Else
                        boxIndex = TextBox1.SelectionStart + TextBox1.SelectionLength - 1
                        charsIndex = textRepresentation(boxIndex)
                    End If
                    arrayToBox()
                    TextBox1.ScrollToCaret()
                Else
                    'Do the highlighting... TODO
                    If boxIndex < textRepresentation.Length - 1 Then
                        If TextBox1.SelectedText = "" Then
                            highlightRight = True
                            HideCaret(TextBox1.Handle)
                            TextBox1.Select(boxIndex, 1)
                            boxIndex -= 1
                        Else
                            If highlightRight Then
                                TextBox1.Select(TextBox1.SelectionStart, TextBox1.SelectionLength + 1)
                            Else
                                TextBox1.Select(TextBox1.SelectionStart + 1, TextBox1.SelectionLength - 1)
                            End If
                        End If
                        boxIndex += 1
                        charsIndex = textRepresentation(boxIndex)
                    End If
                    TextBox1.ScrollToCaret()
                    Return True
                End If
            ElseIf keyData = Keys.Left Or keyData = Keys.NumPad4 Then
                If Not enterKeyDown Then
                    If TextBox1.SelectedText = "" Then
                        If boxIndex > 0 Then
                            arrowsPressedWithEnter = True
                            HideCaret(TextBox1.Handle)
                            boxIndex -= 1
                            charsIndex = textRepresentation(boxIndex)
                        End If
                    Else
                        boxIndex = TextBox1.SelectionStart
                        charsIndex = textRepresentation(boxIndex)
                    End If
                    arrayToBox()
                    TextBox1.ScrollToCaret()
                Else
                    'Do the highlighting
                    If boxIndex > 0 Then
                        If TextBox1.SelectedText = "" Then
                            highlightRight = False
                            HideCaret(TextBox1.Handle)
                            TextBox1.Select(boxIndex, 1)
                            boxIndex += 1
                        Else
                            If highlightRight Then
                                TextBox1.Select(TextBox1.SelectionStart, TextBox1.SelectionLength - 1)
                            Else
                                TextBox1.Select(TextBox1.SelectionStart - 1, TextBox1.SelectionLength + 1)
                            End If
                        End If
                        boxIndex -= 1
                        charsIndex = textRepresentation(boxIndex)
                    End If
                    TextBox1.ScrollToCaret()
                    Return True
                End If
            ElseIf keyData = Keys.Enter Or keyData = Keys.NumPad5 Then
                enterKeyDown = True
                If enterKeyDown And arrowsPressedWithEnter Then
                    arrowsPressedWithEnter = False
                Else
                    If Not ContextMenuStrip1.Visible Then
                        ContextMenuStrip1.Show(TextBox1, TextBox1.Location.X + TextBox1.Width, TextBox1.Location.Y)
                    End If
                End If
            End If
        End If
        Return True
    End Function

    Protected Overrides Function ProcessDialogKey(ByVal keyData As System.Windows.Forms.Keys) As Boolean
        Return True
    End Function

    Protected Overrides Function ProcessKeyPreview(ByRef m As System.Windows.Forms.Message) As Boolean
        If m.Msg = WM_KEYUP Or m.Msg = WM_SYSKEYUP Then
            If (m.WParam = Keys.Enter) Or (m.WParam = Keys.NumPad5) Then

                If Not mouseReleased Then
                    mouseReleased = True
                End If

                enterKeyDown = False
                Return True
            End If
        Else
            Return MyBase.ProcessKeyPreview(m)
        End If
    End Function

    Private Sub arrayToBox()
        Dim arrayLength As Integer = textRepresentation.Length

        TextBox1.Text = chars(textRepresentation(0))
        For counter As Integer = 1 To arrayLength - 1
            TextBox1.Text = TextBox1.Text + chars(textRepresentation(counter))
        Next
        TextBox1.Select(boxIndex + 1, 0)
    End Sub

    Public Sub Clear()
        'Clear the internal representation
        ReDim textRepresentation(0)

        'Update the state variables
        boxIndex = 0
        charsIndex = textRepresentation(boxIndex)

        'Update the screen representation
        arrayToBox()
    End Sub

    Public Sub SelectAll()
        TextBox1.SelectAll()
    End Sub

    Public Sub Cut()
        If (TextBox1.SelectedText = "") Then
            MsgBox("Please select text to cut.", MsgBoxStyle.Exclamation)
        Else
            My.Computer.Clipboard.SetText(TextBox1.SelectedText.ToLower)
            DeleteHighlightedText()
        End If
    End Sub

    Public Sub Copy()
        If (TextBox1.SelectedText = "") Then
            MsgBox("Please select text to copy.", MsgBoxStyle.Exclamation)
        Else
            My.Computer.Clipboard.SetText(TextBox1.SelectedText.ToLower)
        End If
    End Sub

    Public Sub Paste()
        Dim pasteText As String = My.Computer.Clipboard.GetText.ToLower
        Dim pasteTextLength As Integer = pasteText.Length - 1
        Dim startIndex As Integer = TextBox1.SelectionStart - 1
        Dim selectLength As Integer = TextBox1.SelectionLength
        Dim endingTextLength As Integer = textRepresentation.Length - 1 - (startIndex + selectLength)
        If endingTextLength < 0 Then
            endingTextLength = 0
        End If

        Dim counter As Integer = 0
        Dim EndingText(endingTextLength) As Integer

        For index As Integer = startIndex + selectLength To textRepresentation.Length - 1
            EndingText(counter) = textRepresentation(index)
            counter += 1
        Next

        ReDim Preserve textRepresentation(startIndex + pasteTextLength + counter)

        counter = startIndex
        For index1 As Integer = 0 To pasteTextLength
            For index2 As Integer = 0 To MAX_CHARS_INDEX - 1
                If pasteText(index1) = chars(index2) Then
                    textRepresentation(counter) = index2
                End If
            Next
            counter += 1
        Next

        counter = 0
        For index As Integer = startIndex + pasteTextLength + 1 To textRepresentation.Length - 1
            textRepresentation(index) = EndingText(counter)
            counter += 1
        Next

        arrayToBox()
    End Sub

    Public Sub DeleteHighlightedText()
        Dim startIndex As Integer = TextBox1.SelectionStart
        Dim selectLength As Integer = TextBox1.SelectionLength
        Dim endingTextLength As Integer = textRepresentation.Length - 1 - (startIndex + selectLength)
        Dim counter As Integer = 0
        Dim endingText(endingTextLength) As Integer

        If selectLength = textRepresentation.Length Then
            Clear()
            Return
        End If

        For index As Integer = startIndex + selectLength To textRepresentation.Length - 1
            endingText(counter) = textRepresentation(index)
            counter += 1
        Next

        ReDim Preserve textRepresentation(textRepresentation.Length - 1 - selectLength)
        counter = startIndex
        For index As Integer = 0 To endingText.Length - 1
            textRepresentation(counter) = endingText(index)
            counter += 1
        Next
        boxIndex -= 1

        If boxIndex < 0 Then
            boxIndex = 0
        ElseIf boxIndex > textRepresentation.Length - 1 Then
            boxIndex = textRepresentation.Length - 1
        End If

        charsIndex = textRepresentation(boxIndex)
        arrayToBox()
    End Sub

    Private Sub CopyToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CopyToolStripMenuItem.Click
        Copy()
    End Sub

    Private Sub SelectAllToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SelectAllToolStripMenuItem.Click
        SelectAll()
        HideCaret(TextBox1.Handle)
    End Sub

    Private Sub ContextMenuStrip1_Opening(ByVal sender As System.Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles ContextMenuStrip1.Opening
        If Not My.Computer.Clipboard.ContainsText() Then
            PasteToolStripMenuItem.Enabled = False
        Else
            PasteToolStripMenuItem.Enabled = True
        End If

        If TextBox1.SelectedText = "" Then
            CopyToolStripMenuItem.Enabled = False
            CutToolStripMenuItem.Enabled = False
        Else
            CopyToolStripMenuItem.Enabled = True
            CutToolStripMenuItem.Enabled = True
        End If

    End Sub

    Private Sub CancelToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CancelToolStripMenuItem.Click
        ContextMenuStrip1.Close()
    End Sub

    Private Sub CutToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CutToolStripMenuItem.Click
        Cut()
    End Sub

    Private Sub ClearToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ClearToolStripMenuItem.Click
        Clear()
    End Sub

    Private Sub DeleteSelectionToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles DeleteSelectionToolStripMenuItem.Click
        DeleteHighlightedText()
    End Sub

    Private Sub PasteToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PasteToolStripMenuItem.Click
        Paste()
    End Sub

    Private Sub LeaveTextboxToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles LeaveTextboxToolStripMenuItem.Click
        Try
            switchTo.Focus()
        Catch exc As NullReferenceException
        End Try
    End Sub

    Public Sub keyMouseUp()

        Dim inputEvents(0) As INPUT_TYPE

        inputEvents(0).xi.dx = 0
        inputEvents(0).xi.dy = 0
        inputEvents(0).xi.mouseData = 0
        inputEvents(0).xi.dwFlags = MOUSE_LEFTUP
        inputEvents(0).xi.dwtime = 0
        inputEvents(0).xi.dwExtraInfo = 0
        inputEvents(0).dwType = INPUT_MOUSE

        SendInput(1, inputEvents(0), Len(inputEvents(0)))
    End Sub

    Public Sub keyMouseMove(ByVal x As Integer, ByVal y As Integer)

        Dim inputEvents(0) As INPUT_TYPE

        inputEvents(0).xi.dx = x
        inputEvents(0).xi.dy = y
        inputEvents(0).xi.mouseData = 0
        inputEvents(0).xi.dwFlags = MOUSE_MOVE
        inputEvents(0).xi.dwtime = 0
        inputEvents(0).xi.dwExtraInfo = 0
        inputEvents(0).dwType = INPUT_MOUSE

        SendInput(1, inputEvents(0), Len(inputEvents(0)))
    End Sub
End Class
